Skip to content

feat(haproxy): render empty-state when no HAProxy-capable boxes - #118

Merged
sarg3nt merged 1 commit into
fix/issue-112-haproxy-tile-capabilitiesfrom
fix/issue-112-haproxy-empty-state
May 17, 2026
Merged

feat(haproxy): render empty-state when no HAProxy-capable boxes#118
sarg3nt merged 1 commit into
fix/issue-112-haproxy-tile-capabilitiesfrom
fix/issue-112-haproxy-empty-state

Conversation

@sarg3nt

@sarg3nt sarg3nt commented May 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • After PR feat(haproxy): filter dashboard tiles by agent haproxy capability #115 filters the HAProxy dashboard by agent capability, the server list can be empty for two distinct reasons: no enabled boxes at all (case A), or enabled boxes exist but none advertise the haproxy gear (case B).
  • Previously both cases redirected to /settings/boxes. Case B is hostile UX — the operator is on /haproxy on purpose and gets bounced away with no explanation. This PR renders an inline InfoAlert for case B pointing at the Bx fleet view and the boxes settings, and keeps the redirect for case A.
  • Touches pages.Overview to accept an optional emptyReason string; updates the single call site. StatusGridPage gets the same redirect-vs-render treatment.

Phase 2 slice of #112. Stacked on #115 — review the parent first; merge order is #115 → this PR.

Test plan

  • go build ./..., go vet ./..., go test -count=1 ./internal/framework/handler/... ./internal/gears/haproxy/... clean
  • On a deployment with zero enabled boxes: visit /haproxy and confirm it still redirects to /settings/boxes (case A, unchanged).
  • On a deployment with enabled boxes but none have HAProxy (e.g. Mjolnir-only): visit /haproxy and confirm it renders with the new InfoAlert instead of redirecting.
  • On a deployment with at least one HAProxy box: visit /haproxy and confirm the per-box tiles render normally (unchanged behavior).

🤖 Generated with Claude Code

…oxy boxes (#112)

Previously, OverviewPage redirected to /settings/boxes whenever its
server list was empty. After the haproxy-capability filter from the
parent commit, that list can be empty for two distinct reasons:

  1. No enabled boxes at all — operator hasn't configured anything.
  2. Enabled boxes exist, but none advertise the haproxy gear (e.g.
     a TrueNAS host running gearbox-agent in a container without
     HAProxy installed).

Redirecting on case 2 is hostile UX: the operator is on /haproxy on
purpose and gets bounced away with no explanation. Render an inline
InfoAlert instead, pointing them at the Bx fleet view and the boxes
settings page so they can either fix the agent or add a different
host.

Touches the Overview templ to accept an optional emptyReason string;
existing call site is the only one and is updated in this commit.
StatusGridPage gets the same redirect-vs-render treatment (its
empty-state markup is the template's own concern).

Phase 2 slice of #112. Stacked on fix/issue-112-haproxy-tile-capabilities
(PR #115).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sarg3nt
sarg3nt merged commit ad83636 into fix/issue-112-haproxy-tile-capabilities May 17, 2026
3 checks passed
sarg3nt added a commit that referenced this pull request May 17, 2026
* feat(haproxy): filter dashboard tiles by agent haproxy capability (#112)

The HAProxy dashboard at /haproxy iterates every enabled server and
attaches `hx-get="/htmx/{box}/stats"` + `/metrics` to each row. On a
box whose agent has no haproxy gear (e.g. container-mode agent on
TrueNAS — see issue #112), those polls return 503 forever and the
tiles stay empty. Light-hugger's deployment exhibits this whenever
mjolnir is in the box roster.

Plumb the framework Handler's CapabilitiesCache through into the
ServerAdapter (wired in main.go via the new SetCapabilitiesCache /
CapabilitiesCache pair) and add GetEnabledServersWithGearAvailable on
the adapter. The HAProxy gear's overview + status-grid handlers now
filter through that, so boxes whose probe table marks haproxy as
unavailable just don't appear on the page.

Fail-open contract: an agent whose capabilities can't be fetched
(unreachable, no API key, TLS verify off) is still included, matching
how filterGearsByAgentCapabilities handles the same case. A transient
agent outage doesn't make the page go dark.

Default landing route per box (the other half of slice 3 in the
roadmap) is left for a follow-up — this PR is scoped to the tile-
filtering symptom.

Phase 2 slice of #112.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(haproxy): address Copilot review — parallelize fetches, correct gear list (#112)

Two Copilot review findings on the original capability-aware filter:

  1. Doc inaccuracy: the example agent-gear list included "services",
     but the agent has no `services` gear (the /api/v1/services
     endpoint is served by the `metrics` gear). Replace with the
     correct list and call out that some dashboard gears (services,
     alerts, bx) don't have agent counterparts and shouldn't be
     filtered via this helper.

  2. O(N * fetchTimeout) cold-cache latency: synchronous fetches
     could stall a page render to N × fetchTimeout when multiple
     agents are unreachable. Parallelize across boxes with one
     goroutine per box and a sync.WaitGroup join, so cold-cache
     renders pay one round of timeout at most. Warm-cache renders
     hit the cache's read-lock and stay sub-millisecond. Box order
     is preserved by writing into a positional `keep []bool` and
     compacting after the wait.

`go test -race` clean on services/handler/agent/haproxy packages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(haproxy): render empty-state instead of redirecting when no HAProxy boxes (#112) (#118)

Previously, OverviewPage redirected to /settings/boxes whenever its
server list was empty. After the haproxy-capability filter from the
parent commit, that list can be empty for two distinct reasons:

  1. No enabled boxes at all — operator hasn't configured anything.
  2. Enabled boxes exist, but none advertise the haproxy gear (e.g.
     a TrueNAS host running gearbox-agent in a container without
     HAProxy installed).

Redirecting on case 2 is hostile UX: the operator is on /haproxy on
purpose and gets bounced away with no explanation. Render an inline
InfoAlert instead, pointing them at the Bx fleet view and the boxes
settings page so they can either fix the agent or add a different
host.

Touches the Overview templ to accept an optional emptyReason string;
existing call site is the only one and is updated in this commit.
StatusGridPage gets the same redirect-vs-render treatment (its
empty-state markup is the template's own concern).

Phase 2 slice of #112. Stacked on fix/issue-112-haproxy-tile-capabilities
(PR #115).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sarg3nt added a commit that referenced this pull request May 17, 2026
…#112) (#133)

RootRedirect's fallback chain hard-coded /haproxy as the dashboard
default for any installation with at least one enabled box. On a
TrueNAS-style deployment whose agent runs in a distroless container
without HAProxy (Mjolnir), users would log in and be dropped on an
empty /haproxy page that 503-storms on the per-box stat tiles.

Make the landing route per-box and capability-aware:

  - /haproxy when the active box's agent reports haproxy available
    (preserves the historical landing for HAProxy-fronted deployments)
  - /metrics when haproxy isn't available but metrics is — the
    next-most-useful single-pane-of-glass for a host-only agent
  - /bx as the universal fallback so an active box with no advertised
    gears still has a place to land

Fail-open to /haproxy when capabilities can't be fetched (agent down,
no API key, older agent that pre-dates probing) so a transient outage
doesn't change the dashboard's behavior.

Per-user / system landing-path overrides (login.go::resolvePostLoginPath)
keep priority over this — a user who has explicitly set their landing
URL still gets it.

Four tests exercise the new helper against a real httptest.Server
returning capability responses: haproxy-prefers-haproxy,
no-haproxy-falls-to-metrics, host-only-falls-to-bx, and
agent-down-fails-open-to-haproxy.

Phase 2 slice of #112. Stacked on fix/issue-112-haproxy-empty-state (#118).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant